This repository has no description
1'use client';
2
3import { useEffect, useState } from 'react';
4import { useRouter, useParams } from 'next/navigation';
5import { getAccessToken } from '@/services/auth';
6import { ApiClient } from '@/api-client/ApiClient';
7import type { GetUrlCardViewResponse } from '@/api-client/types';
8import {
9 Button,
10 Loader,
11 Stack,
12 Text,
13 Card,
14 Title,
15 Anchor,
16 Blockquote,
17 Box,
18 Group,
19 Badge,
20 Image,
21 Divider,
22} from '@mantine/core';
23
24export default function CardPage() {
25 const [card, setCard] = useState<GetUrlCardViewResponse | null>(null);
26 const [loading, setLoading] = useState(true);
27 const [error, setError] = useState('');
28 const router = useRouter();
29 const params = useParams();
30 const cardId = params.cardId as string;
31
32 useEffect(() => {
33 // Create API client instance
34 const apiClient = new ApiClient(
35 process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3000',
36 () => getAccessToken(),
37 );
38
39 const fetchCard = async () => {
40 try {
41 setLoading(true);
42 const response = await apiClient.getUrlCardView(cardId);
43 setCard(response);
44 } catch (error: any) {
45 console.error('Error fetching card:', error);
46 setError(error.message || 'Failed to load card');
47 } finally {
48 setLoading(false);
49 }
50 };
51
52 if (cardId) {
53 fetchCard();
54 }
55 }, [cardId]);
56
57 if (loading) {
58 return <Loader />;
59 }
60
61 if (error || !card) {
62 return (
63 <Stack align="center">
64 <Text c={'red'}>{error || 'Card not found'}</Text>
65 <Button onClick={() => router.back()}>Go Back</Button>
66 </Stack>
67 );
68 }
69
70 return (
71 <Box>
72 <Stack>
73 <Group>
74 <Button variant="outline" onClick={() => router.back()}>
75 ← Back
76 </Button>
77 </Group>
78
79 <Card withBorder>
80 <Stack>
81 <Stack>
82 <Group wrap="nowrap">
83 <Stack>
84 <Text fz={'xl'} fw={600}>
85 {card.cardContent.title || 'Untitled'}
86 </Text>
87 <Group gap={'xs'}>
88 <Badge variant="outline">{card.type}</Badge>
89 <Text fz={'sm'} c={'gray'}>
90 Added {new Date(card.createdAt).toLocaleDateString()}
91 </Text>
92 </Group>
93 </Stack>
94 {card.cardContent.thumbnailUrl && (
95 <Image
96 src={card.cardContent.thumbnailUrl}
97 alt={card.cardContent.title || 'Card thumbnail'}
98 w={128}
99 radius={'md'}
100 />
101 )}
102 </Group>
103 </Stack>
104 <Stack gap={'xl'}>
105 {/* URL */}
106 <Stack gap={'xs'}>
107 <Title order={3} fz={'sm'}>
108 URL
109 </Title>
110 <Anchor
111 c="blue"
112 href={card.url}
113 target="_blank"
114 rel="noopener noreferrer"
115 >
116 {card.url}
117 </Anchor>
118 </Stack>
119
120 {/* Description */}
121 {card.cardContent.description && (
122 <Stack gap={'xs'}>
123 <Title order={3} fz={'sm'}>
124 Description
125 </Title>
126 <Text c={'gray'}>{card.cardContent.description}</Text>
127 </Stack>
128 )}
129
130 {/* Author */}
131 {card.cardContent.author && (
132 <Stack gap={'xs'}>
133 <Title order={3} fz={'sm'}>
134 Author
135 </Title>
136 <Text c={'gray'}>{card.cardContent.author}</Text>
137 </Stack>
138 )}
139
140 {/* Note */}
141 {card.note && (
142 <Stack gap={'xs'}>
143 <Title order={3} fz={'sm'}>
144 Note
145 </Title>
146 <Blockquote color="yellow" p={'xs'} fz={'sm'}>
147 {card.note.text}
148 </Blockquote>
149 </Stack>
150 )}
151
152 {/* Collections */}
153 {card.collections && card.collections.length > 0 && (
154 <Stack gap={'xs'}>
155 <Title order={3} fz={'sm'}>
156 Collections
157 </Title>
158 <Group>
159 {card.collections.map((collection) => (
160 <Badge
161 key={collection.id}
162 variant="outline"
163 onClick={() =>
164 router.push(`/collections/${collection.id}`)
165 }
166 >
167 {collection.name}
168 </Badge>
169 ))}
170 </Group>
171 </Stack>
172 )}
173
174 {/* Metadata */}
175 <Divider />
176
177 <Group justify="space-between">
178 <Text fz={'sm'} fw={500} c={'gray'}>
179 Created: {new Date(card.createdAt).toLocaleString()}
180 </Text>
181 <Text fz={'sm'} fw={500} c={'gray'}>
182 Updated: {new Date(card.updatedAt).toLocaleString()}
183 </Text>
184 </Group>
185 </Stack>
186 </Stack>
187 </Card>
188 </Stack>
189 </Box>
190 );
191}